/**************************************************************************** The following code was taken from the Network Developer's Resource, published by RoseWare. All contents are Copyright (c) 1993, RoseWare. All Rights Reserved. This material is made available as a service for subscribers of the Network Developer's Resource. This material is not public domain. Simply put, if you are not a subscriber of the Network Developer's Resource, you are using this material illegally. Those interested in subscribing should contact RoseWare via: CompuServe: 76711,110 Internet: 76711.110@compuserve.com Phone: (704) 258-9166 Fax: (704) 258-9374 BBS: (704) 258-8675 US Mail: P.O. Box 8564 Asheville, NC 28814-8564 Also see the file SUBSCRIB.TXT in this ZIP file... ****************************************************************************/ ==> Calling the VLM shell from Borland Pascal <== Well, the VLM shells are out and you're wondering, "How do I program for them? How do I make my code VLM smart?" As of the time that I am writing this article, Novell has released very little information as to how to interface into the VLM APIs. What little has been released has only gone out to a few people and the information is very incomplete and not very accurate. But, with what information I do have and what I've managed to put together of other prominate NetWare nerds, I've managed to get quite a few things working. The code presented here is the basic interface into the VLM shell and lets you at least talk to it from Pascal. Once you get that far you can then start digging around for whatever API information you can come up with. And, when you finally get the API information and you start making the new calls, you find that many of them either don't work, or barely work. So you have to be careful to program around the bugs and try to compensate by building smarts around a dumb API. I am currently using VLM 1.1 and it still, in my opinion, isn't ready yet. But, it is close enough to see the light at the end of the tunnel. Some day VLM shells are going to work and be great. So it's worth starting to move in that direction. In my opinion, Novell has really failed the third party developers on these VLM shells. API information should have been available BEFORE these shells were ever released. So here it is almost a year AFTER and the information is rare, inaccurate, and incomplete. Novell has made no commitments as to when this situation will be fixed. I therefore encourage to to complain bitterly to Novell and encourage them to fix the problem, for their sake, as well as ours. THE VLM INTERFACE ----------------- The NETX shell was called by using Int 21 calls. The VLM shell is different. The first thing you do is an Int 2F call to get the address if the VLM API interface. Once you have that address, you use it to talk to the VLM. If this call fails then you're running under NETX and should then make the NETX call. To call the VLM you push the VLM functions on the stack and call the VLM address. The assembler listing here is code I modified from the Turbopower Software Object Professional Library. If you're programming in Pascal, this is a "must have" library. It has everything in it and then some. If you don't have it already, get it. With the NETX shells you created Request and Reply buffers with the first two bytes being the buffer length. You then passed registers pointing to these buffers. The VLM shells are different. Here the registers point to a "fragment list" which is a list of pointers and buffers lengths to send and receive data. Why you would want to do this, I don't know. But that's the way it is. What I did in my interface was to create a structure that is like the old call, and translate it to the new call. I use a single fragment in my fragment list. I still pass the Request and Reply buffers with the first two bytes being the buffer length. But if I make a VLM call, I read the length word into the fragment list and move the buffer pointer forward to bytes to point to the data. Most Netware calls use either $E1, $E2 or $E3 in the AH register. These calls are directly translated into VLM calls $15, $16 and $17. By looking at my examples, you can create VLM smart calls for most of your existing functions and will allow you to make your code somewhat VLM smart while we are still waiting for Novell to release "the good stuff". ASSEMBLER CODE -------------- ;****************************************************** ; OPVLM.ASM 1.21 ; Vlm Interface routines ; Copyright (c) TurboPower Software 1987, 1992. ; Portions Copyright (c) Sunny Hill Software 1985, 1986 ; and used under license to TurboPower Software ; All rights reserved. ; Portions Copyright (c) Marc Perkel / Computer Tyme 1993 ; ; Computer Tyme * 417-866-1222 ; TurboPower Software * 719-260-6641 ; ;****************************************************** ;****************************************************** Equates ;Fields in an IntRegisters variable BpR = 0 EsR = 2 DsR = 4 DiR = 6 SiR = 8 DxR = 10 CxR = 12 BxR = 14 AxR = 16 Flags = 22 ;****************************************************** Data DATA SEGMENT BYTE PUBLIC DATA ENDS ;****************************************************** Code CODE SEGMENT BYTE PUBLIC ASSUME CS:CODE, DS:DATA PUBLIC CallVlm ;****************************************************** Structures ;Structure of a pointer Pointer STRUC Ofst DW 0 Segm DW 0 Pointer ENDS ;****************************************************** EmulateInt ;procedure CallVlm(var Regs : IntRegisters; IntAddr : Pointer; ; DestID, DestFun : Word); ;Emulates an interrupt by filling the CPU registers with the values in Regs, ;clearing interrupts, pushing the flags, and calling far to IntAddr. ;Equates for parameters destFun EQU word ptr [BP+6] destID EQU word ptr [BP+8] IntAddr EQU DWORD PTR [BP+10] Regs EQU DWORD PTR [BP+14] Regs2 EQU DWORD PTR [BP+20] ;Regs after the 'interrupt' - pushed ;BP, DS, and flags figure into ;equation the second time around ;Temporary variable stored in the code segment IntAddress Pointer <> CallVlm PROC FAR PUSH BP ;Save BP MOV BP,SP ;Set up stack frame PUSHF ;Save flags PUSH DS ;Save DS ;Load registers with contents of Regs PUSH 0 ;VLM stack parameters PUSH DestID PUSH DestFun LDS DI,Regs ;DS:DI points to Regs MOV AH,[DI].Flags ;Get new flags into AH SAHF ;Load flags from AH MOV BX,[DI].BxR ;Load BX MOV CX,[DI].CxR ;Load CX MOV DX,[DI].DxR ;Load DX ;Set up for the far call with interrupts off -- this section of code ;is not re-entrant LES SI,IntAddr ;ES:SI points to IntAddr CLI ;Interrupts off PUSH DS PUSHF MOV AX,SEG DATA MOV DS,AX MOV AX,CS MOV DS,AX MOV DS:IntAddress.Ofst,SI ;Save offset of IntAddr MOV DS:IntAddress.Segm,ES ;Save segment POPF POP DS MOV AX,[DI].AxR ;Load AX MOV BP,[DI].BpR ;Load BP MOV SI,[DI].SiR ;Load SI MOV ES,[DI].EsR ;Load ES PUSH [DI].DsR ;PUSH new DS MOV DI,[DI].DiR ;Load DI POP DS ;POP new DS ;Emulate interrupt CALL DWORD PTR CS:IntAddress ;Call IntAddr ;Get ready to load Regs PUSH BP ;Save BP MOV BP,SP ;Set up stack frame PUSHF ;Save Flags PUSH ES ;Save ES PUSH DI ;Save DI ;Load Regs with new values LES DI,Regs2 ;ES:DI points to Regs ADD DI,AxR ;ES:DI points to Regs.AX STD ;Go backward STOSW ;Store AX MOV AX,BX ;Get BX into AX and store it STOSW MOV AX,CX ;Get CX into AX and store it STOSW MOV AX,DX ;Get DX into AX and store it STOSW MOV AX,SI ;Get SI into AX and store it STOSW POP AX ;POP saved DI into AX STOSW MOV AX,DS ;Get DS into AX and store it STOSW POP AX ;POP saved ES into AX STOSW ;ES:DI now points to Regs.BP POP AX ;POP saved Flags into AX POP ES:[DI] ;POP saved BP into place MOV ES:[DI].Flags,AL ;Store low byte of flags CLD ;Clear direction flag ;Clean up and return POP DS ;Restore DS POPF ;Restore flags POP BP ;Restore BP RET 12 CallVlm ENDP CODE ENDS END PASCAL INTERFACE CODE --------------------- This code compiles a Turbo Pascal Unit that you include with your program. By using the TransportCallEx procedures you should be able to make your programs somewhat VLM smart. {Novell VLM Shell Interface Routines ;****************************************************** ; VLM.PAS ; Vlm Interface routines ; Copyright (c) TurboPower Software 1987, 1992. ; Portions Copyright (c) Sunny Hill Software 1985, 1986 ; and used under license to TurboPower Software ; All rights reserved. ; Portions Copyright (c) Marc Perkel / Computer Tyme 1993 ; ; Computer Tyme * 417-866-1222 ; TurboPower Software * 719-260-6641 ; ;******************************************************} {$F+} Unit VLM; Interface type Dummy5 = array[1..5] of Word; IntRegisters = record case Byte of 1 : (BP, ES, DS, DI, SI, DX, CX, BX, AX, IP, CS, Flags : Word); 2 : (Dummy : Dummy5; DL, DH, CL, CH, BL, BH, AL, AH : Byte); end; Str80 = String[80]; WordPtr = ^Word; OS = record O, S : Word; end; var NovResult : Byte; VlmAddr : Pointer; {Procedures Exported} Procedure VlmCall (var Regs : IntRegisters; DestID, DestFunc : Word); Procedure TransportCallE1 (Request, Reply : Pointer); Procedure TransportCallE2 (Request, Reply : Pointer); Procedure TransportCallE3 (Request, Reply : Pointer); Function ConTableSize : Byte; Function ConsoleOperator : Boolean; Function LoginName (Conn : Word) : String; Function NovConnection : Word; Function NovBinderyAccess : Byte; Function NumberOfPrinters : Byte; Function ObjectID (ObjName : Str80; T : Word) : LongInt; Function ObjectName (I : LongInt) : String; Function VlmLoaded : Boolean; Implementation Uses Dos; {When this unit is initialized the variable VlmAddr is set to point to the VLM shell call address if the VLM shell is found. If it isn't found, the address is set ti nil.} Function VlmLoaded : Boolean; begin VlmLoaded := VlmAddr <> nil; end; {This routine interfaces with VLMCALL.OBJ which pushes parameters on the stack and calls the VLM Address} {$L VLMCALL.OBJ} Procedure CallVlm (var Regs : IntRegisters; IntAddr : Pointer; DestID, DestFunc : Word); external; Procedure VlmCall (var Regs : IntRegisters; DestID, DestFunc : Word); begin CallVlm(Regs,VlmAddr,DestID,DestFunc); NovResult := Regs.AX; end; {VLM calls use server connection handles. This function returns the connection handle of the default server.} Function DefaultConnectionHandle : Word; var NovRegs : IntRegisters; begin with NovRegs do begin BX := 1; VlmCall(NovRegs,$43,6); DefaultConnectionHandle := CX; end; end; {From Turbopower Software's OPINLINE.PAS program} function PtrToLong(P : Pointer) : LongInt; {-Convert pointer, in range $0:$0 to $FFFF:$000F, to LongInt} begin PtrToLong := (LongInt(OS(P).S) shl 4)+OS(P).O; end; function LongToPtr(L : LongInt) : Pointer; {-Return LongInt L as a normalized pointer} begin LongToPtr := Ptr(Word(L shr 4), Word(L and $F)); end; function AddLongToPtr(P : Pointer; L : LongInt) : Pointer; {-Add a LongInt to a pointer, returning a normalized pointer} begin AddLongToPtr := LongToPtr(L+PtrToLong(P)); end; {When using NETX calls, the DS:SI registers point to the Request Buffer and ES:DI points the the Reply Buffer. The first word of these buffers is the buffer size. The VLM shells are different. The DS:SI registers point to a fragment list containing up to 5 fragments for the data to be sent, and ES:DI points to a fragment list of up to 5 fragments to receive data. Why Novell does this escapes me. So I create a list with a single request and reply fragment. These fragments no longer have the fragment length as the first word and this makes it difficult to make a single interface that is smart enough to make either a VLM call or a NetX call. What I do is build a Request buffer with the first word for the length. If I then make a VLM call, I move the pointer two bytes forward past the length word and copy the length word into the fragment list. This way the old request and reply buffers still work with the new VLM shells.} var ReqBuf : Pointer; ReqBufSize : Word; RepBuf : Pointer; RepBufSize : Word; Procedure VLMTransportCall (Request, Reply : Pointer; Func : Word); var NovRegs : IntRegisters; P : Pointer; begin ReqBuf := Request; ReqBufSize := WordPtr(Request)^; RepBuf := AddLongToPtr(Reply,2); RepBufSize := WordPtr(Reply)^; with NovRegs do begin CX := DefaultConnectionHandle; DS := Seg(ReqBuf); SI := Ofs(ReqBuf); ES := Seg(RepBuf); DI := Ofs(RepBuf); BX := 0; DX := 0; if ReqBufSize > 0 then BX := 1; if RepBufSize > 0 then DX := 1; AX := Func; end; VlmCall(NovRegs,$20,6); end; Procedure NetxTransportCall (Request, Reply : Pointer; Func : Byte); var Regs : Registers; begin with Regs do begin DS := Seg(Request^); SI := Ofs(Request^); ES := Seg(Reply^); DI := Ofs(Reply^); AH := Func; MsDos(Regs); NovResult := AL; end; end; {All $E1 transport calls are mapped to VLM calls 21. Likewise, $E2 is mapped to 22, and $E3 is mapped to 23. The difference in call numbers is a value of $CC.} Procedure TransportCall (Request, Reply : Pointer; B : Byte); begin if VlmLoaded then begin VlmTransportCall(Request,Reply,B - $CC); end else begin NetxTransportCall(Request,Reply,B); end; end; Procedure TransportCallE1 (Request, Reply : Pointer); begin TransPortCall(Request,Reply,$E1); end; Procedure TransportCallE2 (Request, Reply : Pointer); begin TransPortCall(Request,Reply,$E2); end; Procedure TransportCallE3 (Request, Reply : Pointer); begin TransPortCall(Request,Reply,$E3); end; Function ConTableSize : Byte; {Returns the size of the connection table} var NovRegs : IntRegisters; begin with NovRegs do begin if VlmLoaded then begin VlmCall(NovRegs,$10,$0F); ConTableSize := DX; end else ConTableSize := 8; end; end; {The VLM shell allows up to 9 printers. (LPT1-LPT9)} Function NumberOfPrinters : Byte; var NovRegs : IntRegisters; begin if VlmLoaded then with NovRegs do begin BX := 1; VlmCall(NovRegs,$42,7); if AL = 0 then NumberOfPrinters := BX else NumberOfPrinters := 3; end else NumberOfPrinters := 3; end; Function NovConnection : Word; {Returns the 16 bit connection number} var Regs : Registers; NovRegs : IntRegisters; begin if VlmLoaded then begin with NovRegs do begin CX := DefaultConnectionHandle; BH := 13; VlmCall(NovRegs,$10,7); NovConnection := DX; end; end else begin with Regs do begin AH := $DC; MsDos(Regs); NovConnection := AL; end; end; end; Function NovBinderyAccess : Byte; var Request : record Len : Word; Fun : Byte; end; Reply : record Len : Word; Access : Byte; Obj : LongInt; end; begin if NovConnection = 0 then begin NovBinderyAccess := 0; exit; end; Request.Len := SizeOf(Request); Request.Fun := $46; Reply.Len := SizeOf(Reply); TransportCallE3(@Request,@Reply); NovBinderyAccess := Reply.Access and 15; end; Procedure ReverseWord (var W : Word); var A : Array[1..2] of Byte absolute W; B : Byte; begin B := A[1]; A[1] := A[2]; A[2] := B; end; Procedure ReverseLongInt (var L : LongInt); var A : Array[1..4] of Byte absolute L; B : Byte; begin B := A[1]; A[1] := A[4]; A[4] := B; B := A[2]; A[2] := A[3]; A[3] := B; end; Procedure FixString (var St : String); begin St[0] := #255; St[0] := char(pred(pos(#0,St))); end; Function LoginName (Conn : Word) : String; var Request : record Len : Word; Fun : Byte; Connection : LongInt; end; Reply : record Len : Word; ID : LongInt; Typ : Word; Name : Array[1..48] of byte; Time : Array[1..10] of Byte; end; St : String; begin Request.Len := SizeOf(Request); if VlmLoaded then Request.Fun := $1C else Request.Fun := $16; Request.Connection := Conn; Reply.Len := SizeOf(Reply); TransportCallE3(@Request,@Reply); move(Reply.Name,St[1],48); FixString(St); LoginName := St; end; Function ObjectName (I : LongInt) : String; var Request : record Len : Word; Fun : Byte; ID : LongInt; end; Reply : record Len : Word; ID : LongInt; Typ : Word; Name : Array[1..48] of Byte; end; St : String; begin with Request do begin Len := SizeOf(Request); Fun := $36; ID := I; end; Reply.Len := SizeOf(Reply); TransportCallE3(@Request,@Reply); move(Reply.Name,St[1],48); FixString(St); ObjectName := St; end; Function ObjectID (ObjName : Str80; T : Word) : LongInt; var Request : record Len : Word; Fun : Byte; Typ : Word; Name : String[48] end; Reply : record Len : Word; ID : LongInt; Typ : Word; Name : Array[1..48] of Byte; end; begin ObjectID := 0; with Request do begin Len := SizeOf(Request); Fun := $35; Typ := T; ReverseWord(Typ); Name := ObjName; end; Reply.Len := SizeOf(Reply); TransportCallE3(@Request,@Reply); if NovResult = 0 then ObjectID := Reply.ID; end; Function ConsoleOperator : Boolean; var Request : record Len : Word; Fun : Byte; end; Reply : Word; begin with Request do begin Len := SizeOf(Request); Fun := $C8; end; Reply := 0; TransportCallE3(@Request,@Reply); ConsoleOperator := NovResult = 0; end; Procedure InitVlm; var W : Array[1..2] of Word absolute VlmAddr; Regs : Registers; begin VlmAddr := nil; with Regs do begin AX := $7A20; BX := 0; Intr($2F,Regs); if AX = 0 then begin W[1] := BX; W[2] := ES; end; end; end; begin InitVlm; end. EVERYTHING HAS A PRICE ---------------------- This code can be distributed free with no charge or royalty under the following conditions: 1) If you produce a commercial or shareware product you send us a free copy of the programs for our own use and provide free upgrades upon request. 2) If you write a book using this information you send us each 2 free copies of the book when published. You also have to credit us so that we have a little something for our ego. You can reach Kim Kokkonen at: TurboPower Software P.O. Box 49009 Colorado Springs, CO 80949-9009 719-260-6641 (voice, Monday-Friday 9AM-5PM Mountian Time) 719-260-7151 (fax) 719-260-9726 (bbs) Compuserve: 76004,2611 PCVENB Section 6 You can reach Marc Perkel at: Computer Tyme 411 North Sherman, Suite 300 Springfield Mo. 65802 417-866-1222 voice 417-866-1665 bbs/fax Compuserve 71333,427 NVENA Section 3 MHS: Marc @ CTyme Internet: Marc @ CTyme.MHS.Compuserve.com